package com.wxl.multi.redis;


import io.lettuce.core.ReadFrom;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.Arrays;

@Configuration
@SuppressWarnings("Duplicates")
public class Cluster1RedisConfig {

    @Value("${spring.redis.cluster1.nodes}")
    private String cluster1Nodes;

    @Value("${spring.redis.cluster1.password}")
    private String cluster1Password;

    @Value("${spring.redis.cluster1.lettuce.pool.max-active}")
    private int cluster1MaxActive;

    @Value("${spring.redis.cluster1.lettuce.pool.max-idle}")
    private int cluster1MaxIdle;

    @Value("${spring.redis.cluster1.lettuce.pool.min-idle}")
    private int cluster1MinIdle;

    @Value("${spring.redis.cluster1.lettuce.pool.max-wait}")
    private Duration cluster1MaxWait;

    @Value("${spring.redis.cluster1.lettuce.topology-refresh.enabled}")
    private boolean cluster1TopologyRefreshEnabled;

    @Value("${spring.redis.cluster1.lettuce.topology-refresh.adaptive}")
    private boolean cluster1TopologyRefreshAdaptive;

    @Value("${spring.redis.cluster1.lettuce.topology-refresh.period}")
    private Duration cluster1TopologyRefreshPeriod;

    @Value("${spring.redis.cluster1.lettuce.topology-refresh.timeout}")
    private Duration cluster1TopologyRefreshTimeout;

    // 创建共享的 ClientResources
    @Bean(destroyMethod = "shutdown")
    public ClientResources clientResources() {
        return DefaultClientResources.builder().build();
    }

    // 配置第一个Redis集群的连接工厂
    @Bean(name = "cluster1RedisConnectionFactory")
    @Primary
    public LettuceConnectionFactory cluster1RedisConnectionFactory(ClientResources clientResources) {
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(Arrays.asList(cluster1Nodes.split(",")));
        if (cluster1Password != null && !cluster1Password.isEmpty()) {
            clusterConfig.setPassword(cluster1Password);
        }
        clusterConfig.setMaxRedirects(3);

        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxTotal(cluster1MaxActive);
        poolConfig.setMaxIdle(cluster1MaxIdle);
        poolConfig.setMinIdle(cluster1MinIdle);
        poolConfig.setMaxWaitMillis(cluster1MaxWait.toMillis());

        ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                .enableAllAdaptiveRefreshTriggers()
                .enablePeriodicRefresh(cluster1TopologyRefreshPeriod) // 每隔5秒拿一次集群的拓扑信息
                .refreshPeriod(cluster1TopologyRefreshPeriod)
                .build();

        // Config TCP KeepAlive  参考 https://help.aliyun.com/zh/redis/product-overview/notice-on-lettuce-update
        int TCP_KEEPALIVE_IDLE = 30;
        int TCP_USER_TIMEOUT = 30;
        SocketOptions socketOptions = SocketOptions.builder()
                .keepAlive(
                        SocketOptions.KeepAliveOptions.builder()
                            .enable()
                            .idle(Duration.ofSeconds(TCP_KEEPALIVE_IDLE))
                            .interval(Duration.ofSeconds(TCP_KEEPALIVE_IDLE / 3))
                            .count(3)
                            .build()
                )
                .tcpUserTimeout(
                        SocketOptions.TcpUserTimeoutOptions.builder()
                            .enable()
                            .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                            .build()
                )
                .build();

        ClusterClientOptions clientOptions = ClusterClientOptions.builder()
                .topologyRefreshOptions(topologyRefreshOptions)
                //redis 命令超时时间 根据自己需要设置这里设置为2s
                .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(2)))
                .autoReconnect(true)
                .socketOptions(socketOptions)
                .nodeFilter(node -> ! node.getFlags().contains(RedisClusterNode.NodeFlag.FAIL))  // 过滤掉集群中失败状态的节点 https://github.com/redis/lettuce/issues/2318
                .build();

        // 使用 LettucePoolingClientConfiguration 来配置连接池
        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofSeconds(3))
                .clientName("测试clientname")
                .clientOptions(clientOptions)
                .poolConfig(poolConfig)
                .readFrom(ReadFrom.ANY)
                .clientResources(clientResources)
                .build();

        return new LettuceConnectionFactory(clusterConfig, clientConfig);
    }

    // 配置第一个Redis集群的模板
    @Bean(name = "cluster1RedisTemplate")
    @Primary
    public RedisTemplate<String, String> cluster1RedisTemplate(@Qualifier("cluster1RedisConnectionFactory") LettuceConnectionFactory connectionFactory) {
        final RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}